﻿/*
Audica Uploader - application for convenient uploading to various file hostings
Copyright (C) 2011-2012 Makarov Andrey (Winand)

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "scriptclasses.h"
#include <QApplication>

ScriptWebRequest::ScriptWebRequest(QNetworkAccessManager* manager, QObject* parent) :
    QObject(parent), netw(manager), reply(0)
{
    Q_ASSERT(netw);
    //if(!netw) qDebug() << "Couldn't get NetworkAccessManager. May crash!";
    connect(this, SIGNAL(resetTimeout(int)), parent, SIGNAL(resetTimeout(int)));
    connect(this, SIGNAL(progress(qint64, qint64)), parent, SIGNAL(progress(qint64, qint64)));
}

void ScriptWebRequest::resetReply() {
    if(reply) {
        delete reply; //abort and free resources
        reply = 0;
    }
}

void ScriptWebRequest::abstract_request(ScriptWebRequest::RequestType type, QNetworkRequest request, QHttpMultiPart* body) {
    resetReply();
    if(type == ScriptWebRequest::RT_GET) {
        reply = netw->get(request);
    } else if(type == ScriptWebRequest::RT_POST && body)
        reply = netw->post(request, body);
    else qWarning() << "ScriptWebRequest::abstract_request" << "Wrong parameters";
    connect(reply, SIGNAL(finished()), SLOT(reply_finished()));
    connect(reply, SIGNAL(uploadProgress(qint64, qint64)), SLOT(uploadProgress(qint64, qint64)));
    connect(reply, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64)));
    emit resetTimeout(TIME_SCRIPT_WAIT);
    /*while(waitingForResponce)
        qApp->processEvents(QEventLoop::WaitForMoreEvents);*/
}

void ScriptWebRequest::post(ScriptRequestData *request, ZHttpMultiPart* body) {
    if(request && body)
        abstract_request(ScriptWebRequest::RT_POST, *request, &body->MultiPart);
    else qWarning() << "ScriptWebRequest::post" << "Wrong parameters";
}

void ScriptWebRequest::get(const QString url) {
    if(!url.isEmpty())
        abstract_request(ScriptWebRequest::RT_GET, QNetworkRequest(QUrl(url)));
    else qWarning() << "ScriptWebRequest::get" << "Empty url";
}

/*void ScriptWebRequest::get(ScriptRequestData *request) {
    if(request)
        abstract_request(ScriptWebRequest::RT_GET, *request);
    else qWarning() << "ScriptWebRequest::get" << "Wrong parameters";
}*/

bool ScriptWebRequest::isRunning() {
    if(reply) return !reply->isFinished();
    else return false;
}

//FIXME: check situations when deleteLater isn't called
void ScriptWebRequest::reply_finished() {
    if(reply->error() == QNetworkReply::NoError) {
        int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
        if(status >= 200 && status < 300) { //success
            qDebug() << "Request finished with" << QString::number(status);
            QTimer::singleShot(0, this, SIGNAL(requestFinished())); //we cannot emit requestFinished here,
                                                                    //cause script can try to delete this object
            emit resetTimeout(TIME_SCRIPT_EXEC); //wait for script execution
        } else if(status >= 300 && status < 400) { //redirection
            QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
            redirectUrl = reply->url().resolved(redirectUrl);
            if(!redirectUrl.isEmpty() && redirectUrl != lastRedirectUrl) {
                lastRedirectUrl = redirectUrl;
                reply->deleteLater(); //cannot delete /reply/ in this proc
                reply = reply->manager()->get(QNetworkRequest(redirectUrl));
                connect(reply, SIGNAL(finished()), SLOT(reply_finished()));
                qDebug() << "Redirected to" << redirectUrl.toString();
                emit resetTimeout(TIME_SCRIPT_WAIT);
            } else qWarning() << "UploadWorker::reply_finished empty or repeated redirection url" << redirectUrl.toString();
        } else qWarning() << "UploadWorker::reply_finished unexpected status" << status;
    } else qDebug() << "UploadWorker::reply_finished err" << reply->error() << reply->errorString();
}

void ScriptWebRequest::uploadProgress(qint64 bytesSent, qint64 bytesTotal) {
    emit resetTimeout(TIME_SCRIPT_WAIT);
    emit progress(bytesSent, bytesTotal);
    qDebug() << "upload" << QString::number((float)bytesSent / bytesTotal * 100, 'f', 2) << "%" << bytesSent << bytesTotal;
}

void ScriptWebRequest::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
    emit resetTimeout(TIME_SCRIPT_WAIT);
    emit progress(bytesReceived, bytesTotal);
    qDebug() << "download" << QString::number((float)bytesReceived / bytesTotal * 100, 'f', 2) << "%" << bytesReceived << bytesTotal;
}

QString ScriptWebRequest::getContents() {
/*    reply->seek(0);
    QFile file(appPath() + "/data/output.html");
    if(file.open(QFile::WriteOnly))
        file.write(reply->readAll());
    qDebug() << "cannot open script file for writing";*/

    reply->seek(0);
    return reply->readAll();
}

QString ScriptWebRequest::getHeader(const QString key) {
    //if(reply->hasRawHeader(key.to...))
    return reply->rawHeader(key.toUtf8());
}

QString ScriptWebRequest::getUrl() {
    return reply->url().toString();
}

ScriptCoreClass::ScriptCoreClass(QScriptEngine* engine_s, QObject* parent) :
    QObject(parent), engine(engine_s), scriptPlugins(0)
{
    //engine->globalObject().setProperty("Enum", engine->newQMetaObject(&ScriptEnums::staticMetaObject));
}

void ScriptCoreClass::setPluginsList(QMap<QString, scriptplugin> *plugins) {
    scriptPlugins = plugins;
}

/*
Create QObject of specified /type/ and add it to script with given /name/
Supported object types: ScriptWebRequest, ZIODevice, ScriptRequestData
*/
bool ScriptCoreClass::newObject(const QString name, const QString type) {
    if(!engine->globalObject().property(name).isValid()) {
        QObject* qobj = 0;
        if(type == "WebRequest")
            qobj = new ScriptWebRequest(getAccessManager(), this);
        else if(type == "PostData")
            qobj = new ZHttpMultiPart(this);
        else if(type == "Request")
            qobj = new ScriptRequestData(this);
        else if(type == "Result")
            qobj = new UploadResult(this);
        else if(scriptPlugins && scriptPlugins->contains(type))
            qobj = scriptPlugins->value(type).instProc();
        else engine->currentContext()->throwError(QScriptContext::ReferenceError, "Unknown object type " + type);
        if(qobj) {
            QScriptValue script_wrap_obj = engine->newQObject(qobj);
            script_wrap_obj.setProperty("Enum", engine->newQMetaObject(qobj->metaObject()));
            engine->globalObject().setProperty(name, script_wrap_obj);
            return true;
        } else return false;
    } else return false; //this name cannot be used
}

bool ScriptCoreClass::checkObjectType(const QString type) {
    if(type == "WebRequest" || type == "PostData" || type == "Request")
        return true;
    else if(scriptPlugins && scriptPlugins->contains(type))
        return true;
    else return false;
}

//Delete object created in newObject procedure
bool ScriptCoreClass::deleteObject(const QString name) {
    QScriptValue obj = engine->globalObject().property(name);
    if(obj.isValid() && obj.isQObject()) {
        engine->globalObject().setProperty(name, QScriptValue());
        QObject* qobj = obj.toQObject();
        delete qobj;
        return true;
    } else return false; //this name isn't used or this isn't a QObject
}

void ScriptCoreClass::alert(const QString str) {
    qWarning() << "alert:" << str;
}

/*void ScriptCoreClass::writeToFile(const QByteArray& str) {
    QFile file(appPath() + "/data/output.html");
    if(!file.open(QFile::WriteOnly)) {
        qDebug() << "cannot open script file for writing";
        return;
    }
    file.write(str);
}*/

void ScriptCoreClass::failed(const QString description) {
    qDebug() << "Script failed:" << description;
}

void ScriptCoreClass::succeeded(const QString description) {
    qDebug() << "Script succeeded:" << description;
    parent()->deleteLater(); //delete uploadworker TODO: emit finished(retval)
}


QNetworkAccessManager* ScriptCoreClass::getAccessManager() {
    QNetworkAccessManager* ret = 0; //default is null
    QMetaObject::invokeMethod(this->parent(), "getAccessManager", Q_RETURN_ARG(QNetworkAccessManager*, ret));
    return ret;
}

/*QByteArray ScriptRequestData::strConv(const QString str, Charsets cs) {
    switch(cs) {
    case CS_ASCII:
        return str.toAscii();
        break;
    case CS_LATIN1:
        return str.toLatin1();
        break;
    case CS_UTF8:
        return str.toUtf8();
    }
    return QByteArray();
}*/
